package dd.lo.dib;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * 参考：https://en.wikipedia.org/wiki/BMP_file_format
 */
public class DibImage {

    private static int getInt(byte b0, byte b1) {
        return (b0 & 0xff) | ((b1 & 0xff) << 8);
    }

    private static int getInt(byte b0, byte b1, byte b2) {
        return (b0 & 0xff) | ((b1 & 0xff) << 8) | ((b2 & 0xff) << 16);
    }

    private static int getInt(byte b0, byte b1, byte b2, byte b3) {
        return (b0 & 0xff) | ((b1 & 0xff) << 8) | ((b2 & 0xff) << 16) | ((b3 & 0xff) << 24);
    }

    private static int getInt(byte[] b) {
        if (b.length == 1) {
            return Byte.toUnsignedInt(b[0]);
        }
        if (b.length == 2) {
            return getInt(b[0], b[1]);
        }
        if (b.length == 3) {
            return getInt(b[0], b[1], b[2]);
        }
        if (b.length == 4) {
            return getInt(b[0], b[1], b[2], b[3]);
        }
        throw new RuntimeException("暂不支持超过4字节的转换");
    }

    private Integer headerLen;

    private Integer width;

    private Integer height;

    private Integer plane;

    private Integer bitsPerPixel;

    //0, no pixel array compression used
    private Integer compression;

    private Integer imageSize;

    private Integer horizontalPrintResolution;

    private Integer verticalPrintResolution;

    //Number of colors in the palette
    private Integer colorTableLen;

    //0 means all colors are important
    private Integer importantColor;

    private Mat imageMat;

    public Integer getHeaderLen() {
        return headerLen;
    }

    public Integer getWidth() {
        return width;
    }

    public Integer getHeight() {
        return height;
    }

    public Integer getPlane() {
        return plane;
    }

    public Integer getBitsPerPixel() {
        return bitsPerPixel;
    }

    public Integer getCompression() {
        return compression;
    }

    public Integer getImageSize() {
        return imageSize;
    }

    public Integer getHorizontalPrintResolution() {
        return horizontalPrintResolution;
    }

    public Integer getVerticalPrintResolution() {
        return verticalPrintResolution;
    }

    public Integer getColorTableLen() {
        return colorTableLen;
    }

    public Integer getImportantColor() {
        return importantColor;
    }

    public Mat getImageMat() {
        return imageMat;
    }

    private byte[] readBytes(InputStream in, int len, String msg) throws IOException {
        byte[] buffer = new byte[len];
        int readCnt = in.read(buffer);
        if (readCnt < len) {
            throw new RuntimeException(msg);
        }
        return buffer;
    }

    public DibImage(InputStream in) throws IOException {
        //读取DIB文件头
        this.headerLen = getInt(readBytes(in, 4, "读取文件头长失败"));
        if (headerLen != 40) throw new RuntimeException("暂不能识别此DIB文件");
        this.width = getInt(readBytes(in, 4, "读取图像宽度失败"));
        this.height = getInt(readBytes(in, 4, "读取图像高度失败"));
        this.plane = getInt(readBytes(in, 2, "读取图层数量失败"));
        this.bitsPerPixel = getInt(readBytes(in, 2, "读取像素长度失败"));
        if (bitsPerPixel != 24) throw new RuntimeException("暂不支持非三通道图像解析");
        this.compression = getInt(readBytes(in, 4, "读取压缩参数失败"));
        this.imageSize = getInt(readBytes(in, 4, "读取文件大小失败"));
        this.horizontalPrintResolution = getInt(readBytes(in, 4, "读取横向分辨率失败"));
        this.verticalPrintResolution = getInt(readBytes(in, 4, "读取纵向分辨率失败"));
        this.colorTableLen = getInt(readBytes(in, 4, "读取调色板失败"));
        this.importantColor = getInt(readBytes(in, 4, "读取重要颜色数量失败"));
        //读取DIB像素内容
        System.out.println("开始读取像素内容...");
        long startTime = new Date().getTime();
        int totalPixels = height * width;
        byte[] redData = new byte[totalPixels];
        byte[] greenData = new byte[totalPixels];
        byte[] blueData = new byte[totalPixels];
        int idx = 0;
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                redData[idx] = (byte) getInt(readBytes(in, 1, "读取像素红色值"));
                greenData[idx] = (byte) getInt(readBytes(in, 1, "读取像素绿色值"));
                blueData[idx] = (byte) getInt(readBytes(in, 1, "读取像素蓝色值"));
                idx++;
            }
        }
        Mat redChannel = new Mat(height, width, CvType.CV_8UC1);
        redChannel.put(0, 0, redData);
        Mat greenChannel = new Mat(height, width, CvType.CV_8UC1);
        greenChannel.put(0, 0, greenData);
        Mat blueChannel = new Mat(height, width, CvType.CV_8UC1);
        blueChannel.put(0, 0, blueData);
        this.imageMat = new Mat();
        //将R、G、B三个通道融合成RGB彩色图片
        Core.merge(Arrays.asList(redChannel, greenChannel, blueChannel), this.imageMat);
        //实测图像数据上线翻转了，因此翻转回来
        Core.flip(imageMat, imageMat, 0); //0-垂直翻转，1-水平翻转
        System.out.printf("像素读取完毕，耗时：%f秒\n", (new Date().getTime() - startTime) / (double)1000);
    }
}
